package org.apache.maven.plugin.deploy;

/*
 *    Copyright 2010 jake pezaro
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.deployer.ArtifactDeployer;
import org.apache.maven.artifact.deployer.ArtifactDeploymentException;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadata;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.model.Build;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.artifact.ProjectArtifactMetadata;
import org.codehaus.plexus.util.xml.Xpp3Dom;

/**
 * Iterates through all projects in a reactor and executes a deploy
 * on each project individually.  Can be used to produce a near-atomic
 * build for a reactor by deferring artifact deployment until the install
 * phase has completed.<br>
 * <br>
 * Usage: <code>mvn clean install deferred-deploy:deploy</code>
 * 
 * @aggregator false
 * @goal deploy
 */
public class DeferredDeployMojo extends AbstractMojo {

	private static final Pattern ALT_REPO_SYNTAX_PATTERN = Pattern.compile( "(.+)::(.+)::(.+)" );
	
	/**
	 * @parameter expression="${project}
	 * @required
	 * @readonly
	 */
	protected MavenProject rootProject;

	/**
	 * Specifies an alternative repository to which the project artifacts should
	 * be deployed ( other than those specified in &lt;distributionManagement&gt; ). <br/>
	 * Format: id::layout::url
	 * 
	 * @parameter expression="${altDeploymentRepository}"
	 */
	private String altDeploymentRepository;

    /**
     * @parameter default-value="${localRepository}"
     * @required
     * @readonly
     */
    private ArtifactRepository localRepository;
    
    /**
     * @component
     */
    private ArtifactDeployer deployer;
    
    /**
     * Component used to create an artifact.
     *
     * @component
     */
    private ArtifactFactory artifactFactory;
	
    /**
     * Component used to create a repository.
     *
     * @component
     */
    private ArtifactRepositoryFactory repositoryFactory;
    
    /**
     * Parameter used to update the metadata to make the artifact as release.
     * 
     * @parameter expression="${updateReleaseInfo}" default-value="false"
     */
    protected boolean updateReleaseInfo;
    
    /**
     * Map that contains the layouts.
     *
     * @component role="org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout"
     */
    private Map<?, ?> repositoryLayouts;
    
    public ArtifactDeployer getDeployer() {
		return deployer;
	}

	public ArtifactRepository getLocalRepository() {
		return localRepository;
	}

	public void setLocalRepository(ArtifactRepository localRepository) {
		this.localRepository = localRepository;
	}

	private ArtifactRepositoryLayout getLayout(String id) throws MojoExecutionException {
		ArtifactRepositoryLayout layout = (ArtifactRepositoryLayout) repositoryLayouts.get(id);
		if (layout == null) {
			throw new MojoExecutionException("Invalid repository layout: " + id);
		}
		return layout;
	}
    
	public void execute() throws MojoExecutionException, MojoFailureException {
		// need to deploy the root project separately as it is not included in the getCollectedProjects() collection
		deployProject(rootProject);

		// deploy each of the reactor projects individually
		for (Object p : rootProject.getCollectedProjects()) {
			if (p instanceof MavenProject) {
				MavenProject project = (MavenProject) p;
				deployProject(project);
			} else {
				getLog().warn("Unrecognised project type: " + p.getClass().getName());
			}
		}
	}

	private boolean isSkip(MavenProject project) {
		return false;
/*	Not tested yet	
		Model model = project.getModel();
		if (model == null) {
			return false;
		}
		Build build = model.getBuild();
		if (build == null) {
			return false;
		}
		Map<?, ?> plugins = build.getPluginsAsMap();
		if (plugins == null) {
			return false;
		}
		Plugin plugin = (Plugin) plugins.get("org.apache.maven.plugins:maven-deploy-plugin");
		if (plugin == null) {
			return false;
		}
		Xpp3Dom configuration = (Xpp3Dom) plugin.getConfiguration();
		if (configuration == null) {
			return false;
		}
		Xpp3Dom skip = configuration.getChild("skip");
		if (skip == null) {
			return false;
		}
		return "true".equalsIgnoreCase(skip.getValue());
*/
	}
	
	private void deployProject(MavenProject project) throws MojoExecutionException, MojoFailureException {
		if (isSkip(project)) {
			getLog().info("Skipping deployment for project: " + project.getId());
			return;
		}
		
		ArtifactRepository deploymentRepository = getDeploymentRepository(project);
		
		if (deploymentRepository == null) {
			throw new MojoExecutionException("Deployment failed: repository element was not specified in the pom inside distributionManagement element");
		}
		
		String protocol = deploymentRepository.getProtocol();
		if (protocol.equals("scp")) {
			File sshFile = new File(System.getProperty("user.home"), ".ssh");
			if (!sshFile.exists()) {
				sshFile.mkdirs();
			}
		}
		
		File pomFile = project.getFile();
		Artifact artifact = project.getArtifact();

		// Deploy the POM
		boolean isPomArtifact = "pom".equals(project.getPackaging());
		if (!isPomArtifact) {
			ArtifactMetadata metadata = new ProjectArtifactMetadata(artifact, pomFile);
			artifact.addMetadata(metadata);
		}

		if (updateReleaseInfo) {
			artifact.setRelease(true);
		}

		try {
			if (isPomArtifact) {
				getDeployer().deploy(pomFile, artifact, deploymentRepository, getLocalRepository());
			} else {
				File file = artifact.getFile();
				if (file != null) {
					getDeployer().deploy(file, artifact, deploymentRepository, getLocalRepository());
				} else {
					if (project.getAttachedArtifacts() != null && project.getAttachedArtifacts().size() > 0) {
	                    Artifact pomArtifact = artifactFactory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getBaseVersion() );
	                    pomArtifact.setFile( pomFile );
	                    if ( updateReleaseInfo ) {
	                        pomArtifact.setRelease( true );
	                    }
	                    getDeployer().deploy(pomFile, pomArtifact, deploymentRepository, getLocalRepository());
					} else {
						throw new MojoExecutionException("Project must a) be packaging == \"pom\", b) have a build artifact assigned or c) have one or more attached artifacts");
					}
				}
			}

			for (Object attchedArtifact : project.getAttachedArtifacts()) {
				Artifact attached = (Artifact) attchedArtifact;
				getDeployer().deploy(attached.getFile(), attached, deploymentRepository, getLocalRepository());
			}
		} catch (ArtifactDeploymentException e) {
			throw new MojoExecutionException(e.getMessage(), e);
		}
	}

	private ArtifactRepository getDeploymentRepository(MavenProject project) throws MojoExecutionException, MojoFailureException {
		ArtifactRepository repo = null;

		if (altDeploymentRepository != null) {
			getLog().info("Using alternate deployment repository " + altDeploymentRepository);
			Matcher matcher = ALT_REPO_SYNTAX_PATTERN.matcher(altDeploymentRepository);
			if (!matcher.matches()) {
				throw new MojoFailureException(altDeploymentRepository, "Invalid syntax for repository.", "Invalid syntax for alternative repository. Use \"id::layout::url\".");
			} else {
				String id = matcher.group(1).trim();
				String layout = matcher.group(2).trim();
				String url = matcher.group(3).trim();
				ArtifactRepositoryLayout repoLayout = getLayout(layout);
				repo = repositoryFactory.createDeploymentArtifactRepository(id, url, repoLayout, true);
			}
		}

		if (repo == null) {
			repo = project.getDistributionManagementArtifactRepository();
		}

		if (repo == null) {
			String msg = "Deployment failed: repository element was not specified in the POM inside distributionManagement element or in -DaltDeploymentRepository=id::layout::url parameter";
			throw new MojoExecutionException(msg);
		}

		return repo;
	}

}
